package objects;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

import org.apache.commons.lang3.ArrayUtils;

import com.lambdaworks.crypto.SCrypt;

import constants.EncryptStandards;
import database.DBManager;

public class User {
    private String propername; // The full name of the user for reports etc, eg. 'John F. Doe'
    private String shortname;  //How the user wants to be called in the GUIm eg. 'John'
     private String username;
     private byte[] cryptokey;

	public User(String username, String password) {
		this.username = username;

		try {
			// Try to get the cryptokey of the user
			this.cryptokey = checkUser(password);
			if(this.cryptokey == null) {
				this.cryptokey = registerUser(password);
			}			
		} catch (UnsupportedEncodingException | GeneralSecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private byte[] registerUser (String password) throws UnsupportedEncodingException, GeneralSecurityException  {
		byte[] newCryptokey = getSaltedScryptedkey(password);
		byte[] scryptSalt = ArrayUtils.subarray(newCryptokey, 0, 16);

		MessageDigest md = MessageDigest.getInstance(EncryptStandards.HashingMethod);
		byte[] hash = md.digest(newCryptokey); //Note: the newCryptokey is already salted via Scrypt, no need to salt it again

		byte[] toBeStored = ArrayUtils.addAll(scryptSalt, hash);

		DBManager.insertUser(this.username, toBeStored);	
		return newCryptokey;
	}

	/**
	 * Authenticate the user with his password, and return his AES key if he is authenticated
	 * @param username The users' username
	 * @param password The password of the user 
	 * @return The bytes of the crytographic key of this user
	 * @throws NoSuchAlgorithmException
	 */
	private byte[] checkUser(String password) throws NoSuchAlgorithmException {
		byte[] saltAndHash = DBManager.getUserHash(this.username);
		// Stop the process if the user was not found
		if(saltAndHash == null) {
			return null;
		}
		byte[] scryptSalt = ArrayUtils.subarray(saltAndHash, 0, 16);
		byte[] hashFromDB = ArrayUtils.subarray(saltAndHash, 16, saltAndHash.length);

		byte[] cryptokey = getScryptedKey(password, scryptSalt);

		MessageDigest md = MessageDigest.getInstance(EncryptStandards.HashingMethod);
		byte[] hash = md.digest(ArrayUtils.addAll(scryptSalt,cryptokey));

		System.out.println("Hash   = "+new String(hash));
		System.out.println("DBHash = "+new String(hashFromDB));

		if(Arrays.equals(hashFromDB, hash)){
			return cryptokey;
		}
		else {
			return null;
		}
	}

	/**
	 * 
	 * @param password
	 * @return A new 32 byte key based upon this password, prepended with a 16 byte salt
	 * @throws UnsupportedEncodingException
	 * @throws GeneralSecurityException
	 */
	private byte[] getSaltedScryptedkey (String password) throws UnsupportedEncodingException, GeneralSecurityException {
		SecureRandom secrand = new SecureRandom();
		byte[] salt = new byte[EncryptStandards.saltLen];
		secrand.nextBytes(salt);

		byte[] key = getScryptedKey(password, salt);		

		return  ArrayUtils.addAll(salt, key);
	}

	/**
	 * 
	 * @param password A user input string
	 * @param salt A 16 byte salt
	 * @return The output of the SCrypt function with the applications default parameters
	 * @throws GeneralSecurityException
	 * @throws UnsupportedEncodingException
	 */
	private byte[] getScryptedKey(String password, byte[] salt) {
		byte[] key = null;
		try {
			key = SCrypt.scrypt(password.getBytes("UTF-8"), salt, EncryptStandards.N, EncryptStandards.r, EncryptStandards.p, EncryptStandards.dkLen);
		} catch (UnsupportedEncodingException | GeneralSecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return key;
	}

}
